feat: support Cursor SDK model params in proof - #178
Conversation
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
- Add @deprecated JSDoc to createModelResolver (silently discards params) - Render modelSelection.params in canvas template (was serialized but unused) - Fix variant tiebreaker: prefer catalog default on score ties - De-duplicate defaultVariant() call in chooseMatchingVariant Change-Id: Ic8dfe2ddc37948affb1b6849428f0a9439daa170
There was a problem hiding this comment.
Stale comment
Found 2 medium-risk issues in the new model-selection support:
parseDAG()/validateModelMap()now canonicalize string model overrides into{ id }objects, which changes the observable return shape of the exported parsing helpers for string-only inputs.- Param validation assumes
Cursor.models.list()always populatesparameters, but the SDK marks that field optional, so models that expose onlyvariantswould reject otherwise valid param selections.Sent by Cursor Automation: Flatbread PR Review
…alogs - Keep string model entries as strings in validateModelSelection output so parseDAG/validateModelMap stay shape-stable for legacy configs; normalize to ModelSelection only via normalizeModelSelection. - When Cursor.models.list() omits parameters but defines variants, validate explicit params by matching a preset variant instead of rejecting supported selections. Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
| } from '@flatbread/proof'; | ||
| ``` | ||
|
|
||
| Note: `createModelResolver` is deprecated in favor of `createModelSelectionResolver` when you need param support (the deprecated helper only returns `ModelSelection.id` and drops `params`). |
There was a problem hiding this comment.
Is there a need for keeping the deprecated function? None of Proof has been released yet so we can change the API however we'd like
| /** | ||
| * Validate a JSON model override without changing its nominal shape: plain | ||
| * strings stay strings so `parseDAG` / `validateModelMap` remain | ||
| * round-trip-stable for legacy configs. Use `normalizeModelSelection` when |
There was a problem hiding this comment.
If necessary, migrate any checked-in legacy configs so we can remove that bit of the comment. Ideally we should avoid legacy config support since Proof has not been released.
| * round-trip-stable for legacy configs. Use `normalizeModelSelection` when | ||
| * you need a `ModelSelection` object (including `{ id }` for strings). | ||
| */ | ||
| export function validateModelSelection( |
There was a problem hiding this comment.
This function is a bit dense, let's break it up into some semantic util functions to make it easier to comprehend
Remove the deprecated model resolver, require object-based model selections in DAG configs, migrate checked-in examples, and split model validation into smaller helpers. Tests: - pnpm -F @flatbread/proof typecheck - pnpm -F @flatbread/proof build - pnpm lint - node --input-type=module runtime probes for validateModelMap/createModelSelectionResolver - node --input-type=module parseDAG checks for checked-in proof examples Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
There was a problem hiding this comment.
Stale comment
Code Review —
feat: support Cursor SDK model params in proofThis is a well-structured feature. The separation of concerns across validation, normalization, catalog-backed resolution, and variant scoring is clean, defensive cloning throughout prevents aliasing bugs, and the eager validation loop at startup (
for (const complexity of COMPLEXITY_KEYS)) fails fast before any agent work begins. The--init-onlybypass for the SDK call is correctly documented. Good work overall.Actionable feedback below (no blockers):
Sent by Cursor Automation: Flatbread PR Review
There was a problem hiding this comment.
Stale comment
Review verdict
REQUEST_CHANGES — one implementation/doc contradiction that will silently break existing
--models-filefiles, one unsupported breaking public-API removal, no error handling on the new startup network call, and zero test coverage for 222+ lines of new branching logic that is correctness-critical.
Chunk-bound feedback
packages/proof/src/dag.ts:511–514
- severity: HIGH
- flagged-by: correctness-and-contracts, proof-runtime-internals
- risk:
validateModelMapcallsvalidateModelSelection, which callsvalidateModelSelectionObjectand rejects any non-object input with "must be a model object." A user's existing--models-fileor inline DAGmodelsthat uses the old plain-string format ("HIGH": "claude-opus-4-7") will throw at parse time with a cryptic error, not a migration notice.normalizeModelSelectionwas introduced precisely to acceptstring | ModelSelection— it is the right function for the parse path.- minimal fix: Replace the
validateModelSelection(value, ...)call invalidateModelMapwithnormalizeModelSelection(value as ModelSpec, ...)so the function accepts both the legacy string format and the new object format.packages/proof/src/index.ts:14
- severity: HIGH
- flagged-by: correctness-and-contracts
- risk:
createModelResolveris removed from the public exports of@flatbread/proofwith no deprecation shim. Any external consumer importing it (including the orchestrator DAG files and any user-authored tooling) gets a TypeScript compile error with no actionable message. The return type also changed from(c) => stringto(c) => ModelSelection, which is an additional silent breakage for consumers who stored the inferred return type.- minimal fix: Re-export
createModelResolveras a deprecated alias:export const createModelResolver = createModelSelectionResolver;with a@deprecatedJSDoc, and bump the minor version with a migration note.packages/proof/src/run_dag.ts:528–543
- severity: MED
- flagged-by: proof-runtime-internals
- risk:
await Cursor.models.list()is called at the top ofmain()with no try/catch. A transient network error, a rate-limit response, or a missingCURSOR_API_KEYat this point kills the entire proof run before any task has executed. For long DAGs (e.g. the PMF audit) this means paying the startup cost with nothing to show for it. The existing CURSOR_API_KEY check two lines above fires before we even reach this code, so the failure mode here is specifically network/rate-limit transience.- minimal fix: Wrap the catalog fetch in a try/catch; on failure, warn to stderr (
[proof] WARNING: catalog validation skipped — Cursor.models.list() failed: <err.message>) and fall back tounresolvedModelForComplexityso the DAG can still launch with unvalidated model ids..cursor/skills/proof/SKILL.md:176
- severity: MED
- flagged-by: correctness-and-contracts, proof-runtime-internals
- risk: Line 176 states "Values can be plain SDK model id strings or SDK model selections with
params", but line 58 in the same file states "Values must be model selection objects." The implementation (validateModelMap) only accepts objects. One of these statements must be wrong — and if the intent is that strings should work (as line 176 and the README imply), then the code has the bug described above in dag.ts:511–514.- minimal fix: Once dag.ts:511–514 is fixed to use
normalizeModelSelection, update line 58 to match line 176: both formats are valid.
Coverage plan
packages/proof/src/__tests__/dag.test.ts— positive:validateModelMapaccepts plain string values ({"HIGH": "claude-opus-4-7"}) after the normalizeModelSelection fixpackages/proof/src/__tests__/dag.test.ts— positive:validateModelMapaccepts full object values ({"HIGH": {"id": "claude-opus-4-7", "params": [...]}})packages/proof/src/__tests__/dag.test.ts— negative:validateModelSelectionrejects null, array, missingid, empty stringidpackages/proof/src/__tests__/dag.test.ts— negative:validateModelParamsrejects duplicate param ids; rejects non-arrayparamspackages/proof/src/__tests__/dag.test.ts— positive:normalizeModelSelectionconverts a plain string to{id: string}and passes an existing object through unchangedpackages/proof/src/__tests__/dag.test.ts— edge (unknown model):resolveModelSelectionFromCatalogthrows with the full known-models list when the id is not in the catalogpackages/proof/src/__tests__/dag.test.ts— positive:resolveModelSelectionFromCatalogreturns clone of selection for a model with no variantspackages/proof/src/__tests__/dag.test.ts— positive:resolveModelSelectionFromCatalogpicks the default variant when no params are requestedpackages/proof/src/__tests__/dag.test.ts— edge (partial params):chooseMatchingVariantscores variants by closeness-to-defaults for un-specified params and picks the best-fit; tie-breaks to the catalog-declared defaultpackages/proof/src/__tests__/dag.test.ts— negative:resolveModelSelectionFromCatalogthrows when requested params match no variantpackages/proof/src/__tests__/dag.test.ts— positive:createCatalogBackedModelResolverreturns a cloned (not the same reference) ModelSelection on repeated calls for the same complexity (cache isolation)packages/proof/src/__tests__/dag.test.ts— negative:resolveModelSelectionFromCatalogthrows when a model has neitherparametersnorvariantsand params are requested
Suggested follow-ups
- Consider whether
--models-fileshould support a"$schema"field pointing to a generated JSON Schema for model selections, so editors can validate the file before running proof.- The
scoreVariantheuristic (prefer defaults for un-specified params) is not documented in a comment; add an inline explanation so future maintainers understand why the scoring works the way it does.
Reviewer scoreboard
correctness-and-contracts: 3 findings, 0 coverage gaps, signal: HIGHproof-runtime-internals: 2 findings, 0 coverage gaps, signal: HIGHtest-coverage-robustness: 0 non-test findings, 12 coverage gaps, signal: HIGHSent by Cursor Automation: Flatbread PR Review
| ? unresolvedModelForComplexity | ||
| : createCatalogBackedModelResolver( | ||
| unresolvedModelForComplexity, | ||
| await Cursor.models.list() |
There was a problem hiding this comment.
No error handling around Cursor.models.list() — a transient failure here kills the entire run before any task executes.
The CURSOR_API_KEY guard a few lines above only checks for a missing env var; it does not protect against network errors, rate limits, or SDK-internal failures from the catalog fetch itself. For long-running DAGs (PMF audit, schema migration), failing at startup with no work done is a bad failure mode.
Suggested approach:
let catalog: readonly ModelCatalogItem[];
try {
catalog = await Cursor.models.list();
} catch (err) {
console.warn(
`[proof] WARNING: catalog validation skipped — Cursor.models.list() failed: ${
err instanceof Error ? err.message : String(err)
}`
);
catalog = [];
}When catalog is empty, resolveModelSelectionFromCatalog will throw for every model id — so you would also need to skip the eager validation loop that follows, or only run it when the catalog is non-empty.
- add tie-break and no-match tests for variant resolution - clarify legacy modelSelection fallback invariant in run_dag - rename generic non-empty string validator for readability - annotate duplicated canvas template model types Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
Co-authored-by: Tony <tonyketcham@users.noreply.github.com>
…tion - Changed model selection format to accept both plain SDK model id strings and model selection objects with parameters. - Updated examples in SKILL.md and README.md to reflect new model id usage. - Enhanced validation functions to support mixed model selection shapes. - Added tests for new model selection behaviors and validation logic. Co-authored-by: Tony <tonyketcham@users.noreply.github.com> Change-Id: Ibed4c110903ded9b94630228eeeb40071822fc82
Change-Id: Id88c02cf45a8fc0206e69deedec0253735245187
There was a problem hiding this comment.
Stale comment
Code Review —
feat: support Cursor SDK model params in proofVerdict:
REQUEST_CHANGES— Two independent consensus HIGH findings across all three reviewer perspectives: (1)SKILL.md:176declares string values valid for themodelsfield whilevalidateModelMapcallsvalidateModelSelectionwhich rejects non-objects, creating a published-doc / enforced-contract break; (2)Cursor.models.list()is called bare at startup with no error handling, meaning a transient network error kills the entire run before any task executes.
Chunk-bound feedback
packages/proof/src/dag.ts:511-514— severity: HIGH — flagged-by: all three perspectives
validateModelMapcallsvalidateModelSelection, which throws"must be a model object"for any plain string.SKILL.md:176explicitly tells users "Values can be plain SDK model id strings." A user who follows the docs will receive a runtime error with no indication the doc is wrong.Minimal fix (pick one and apply it everywhere):
- Option A — "objects only" (least code change): keep
validateModelSelectionbut remove the "plain SDK model id strings" clause fromSKILL.md:176, and updateSKILL.md:58to say "must be model selection objects" consistently.- Option B — "strings or objects": change
validateModelMapto callnormalizeModelSelection(value, ...)instead, which accepts both, then fixSKILL.md:58to match.Either way, add a negative test:
validateModelMap({ HIGH: "claude-opus-4-7", ... })should throw (option A) or pass (option B), pinning whichever contract is chosen.
packages/proof/src/run_dag.ts:530-543— severity: HIGH — flagged-by: proof-runtime-internals, test-coverage-robustness
await Cursor.models.list()is called unconditionally with no try/catch. A transient network hiccup, rate-limit response, or expiredCURSOR_API_KEYcauses an unhandled rejection that terminates the process before a single task runs, with no recovery path and no user-actionable message.Minimal fix: Wrap in try/catch and throw a clear error:
"Could not fetch Cursor model catalog — check CURSOR_API_KEY and network connectivity. Original error: ...".
packages/proof/src/canvas_writer.ts:236-246— severity: MED — flagged-by: proof-runtime-internals
ModelParameterValueandModelSelectionare redeclared inside the canvas template with only a// Keep in synccomment as guard. TypeScript cannot inspect the embedded string template. Ifdag.tsadds a field to either interface, the canvas will compile fine but silently render stale data.Minimal fix: Add a type-level assertion in
canvas_writer.ts(outside the template string):type _AssertModelSelectionSync = import('./dag.js').ModelSelection extends ModelSelection ? true : never;to make divergence a compile error. Long-term: extract the template into a file that can import fromdag.tsdirectly.
Coverage plan (critical gaps)
packages/proof/src/dag.test.ts— negative —validateModelMap({ HIGH: "claude-opus-4-7", ... })behavior must be pinned (throw or not) based on the chosen contract.packages/proof/src/dag.test.ts— positive —validateModelMap({ HIGH: { id: 'x' }, MED: { id: 'y' }, LOW: { id: 'z' } })round-trips correctly (zero tests for the happy path).packages/proof/src/dag.test.ts— negative —validateModelSelection({})throws with label-prefixed message;validateModelSelection({ id: '' })throws;validateModelSelection({ id: 'x', params: 'bad' })throws.packages/proof/src/dag.test.ts— positive —createModelSelectionResolver()with no overrides returnsDEFAULT_MODEL_MAP[c]shape for each of HIGH/MED/LOW.packages/proof/src/dag.test.ts— positive —createModelSelectionResolver({ HIGH: { id: 'x' } })returns override for HIGH and defaults for MED/LOW.packages/proof/src/dag.test.ts— positive —formatModelSelection({ id: 'x' })→'x'; with params →'x (effort=max)'.packages/proof/src/dag.test.ts— positive —createCatalogBackedModelResolverresolves all three complexities; propagates error on unknown model id.packages/proof/src/dag.test.ts— edge:empty-params-stripping —normalizeModelSelection({ id: 'x', params: [] })returns{ id: 'x' }with noparamskey.packages/proof/src/dag.test.ts— edge:duplicate-params —normalizeModelSelectionwith two params sharing the sameidthrows with/duplicate id/.packages/proof/src/dag.test.ts— edge:cache-isolation — second call tocreateCatalogBackedModelResolverfor the same complexity isdeepEqualbut not===the first call.packages/proof/src/dag.test.ts— edge:legacy-resume-path —taskModelSelectionwithts.modelSelectionabsent andts.model: "claude-opus-4-7"produces{ id: "claude-opus-4-7" }.packages/proof/src/dag.test.ts— edge:both-declared —resolveModelSelectionFromCatalogfor a catalog item with bothparametersandvariantsresolves via the variant path.
Suggested follow-ups (out of scope)
--models-fileparsing path: Confirm it also callsvalidateModelMap(or equivalent) and not an older string-only parser.- SDK
paramsruntime handling: TypeScript structural compatibility is confirmed, but whether the SDK actually sendsparamsto the inference endpoint is not testable from this repo. Add an integration note or smoke test once the SDK behavior is confirmed.Cursor.models.list()retry policy: Once the try/catch is added, evaluate whether one retry with exponential backoff is warranted for this startup-blocking call.- canvas_writer.ts long-term: Extract the template into a separate file that can import from
dag.tsdirectly.
Reviewer scoreboard
- proof-runtime-internals: 3 findings, 5 coverage gaps, signal: HIGH
- correctness-and-contracts: 3 findings, 6 coverage gaps, signal: HIGH
- test-coverage-robustness: 2 findings, 14 coverage gaps, signal: MED
Sent by Cursor Automation: Flatbread PR Review
| ? unresolvedModelForComplexity | ||
| : createCatalogBackedModelResolver( | ||
| unresolvedModelForComplexity, | ||
| await Cursor.models.list() |
There was a problem hiding this comment.
Cursor.models.list() called bare with no error handling — run-killing reliability gap (consensus HIGH, flagged by two perspectives).
A transient network error, expired CURSOR_API_KEY, or rate-limit response here causes an unhandled rejection that terminates the entire process before a single task executes. The CURSOR_API_KEY guard a few lines above only checks for an empty string — it does not protect against runtime SDK failures.
Minimal fix:
let catalog: Awaited<ReturnType<typeof Cursor.models.list>>;
try {
catalog = await Cursor.models.list();
} catch (err) {
throw new Error(
`Could not fetch Cursor model catalog — check CURSOR_API_KEY and network connectivity. Original error: ${err}`
);
}| type TaskKind = 'task' | 'pause' | 'oracle'; | ||
|
|
||
| // Keep in sync with ModelParameterValue / ModelSelection in dag.ts. | ||
| interface ModelParameterValue { |
There was a problem hiding this comment.
ModelParameterValue and ModelSelection redeclared without compile-time sync enforcement (MED).
The // Keep in sync comment is the only guard. If dag.ts adds a required field to ModelSelection, TypeScript will not warn here — the local interface shadows any import, and the embedded template string is not typechecked by the package's own tsconfig. Silent canvas rendering anomalies would be the only symptom.
Minimal fix: add a structural assertion outside the template string:
type _AssertModelSelectionInSync =
import('./dag.js').ModelSelection extends ModelSelection ? true : never;This turns any structural divergence into a compile error that the package's own typecheck script will catch.
There was a problem hiding this comment.
Stale comment
Review verdict
REQUEST_CHANGES — two prior findings remain unaddressed: (1)
Cursor.models.list()is called without error handling, meaning any transient SDK/network failure kills the entire DAG run before a single task starts; (2)ModelParameterValueandModelSelectionare still duplicated verbatim inside the canvas template string with only a comment as the sync guard.
Good progress since last run
validateModelIdcorrectly renamed tovalidateNonEmptyString— removes misleading specificity.taskModelSelectionlegacy fallback now carries an explanatory comment; thets.modelassumption (always a bare id in pre-this-PR persisted state) is documented.- Tie-breaking tests added: highest-scoring variant wins, equal-score tie breaks to catalog default. Both paths covered.
validateModelMapnow routes throughnormalizeModelSelection, accepting both plain string and object inputs.- SKILL.md documentation contradiction resolved — "can be plain strings or objects" is used consistently.
Chunk-bound feedback
packages/proof/src/run_dag.ts:534
- severity: HIGH
- flagged-by: proof-runtime-internals
- risk: A network timeout, rate-limit response, or SDK-internal error from
Cursor.models.list()propagates as an unhandled rejection, terminating the process and discarding the DAG entirely — no partial results, no resumable state, no user-readable error.- minimal fix: Wrap in
try/catch; on error, log a warning and fall back tounresolvedModelForComplexity(skipping catalog validation), or at minimum surface a clear error message before exiting rather than a raw stack trace.
packages/proof/src/canvas_writer.ts:236–244
- severity: MED
- flagged-by: proof-runtime-internals
- risk:
ModelParameterValueandModelSelectionare duplicated inside the embedded canvas template string. TypeScript does not enforce their parity with the exported types indag.ts. A field added toModelSelection(e.g., adisplayNamehint for the canvas) will silently be absent from canvas rendering.- minimal fix: Add a codegen or
satisfies-style sanity check, or at minimum expand the// Keep in synccomment to list the exact fields that need mirroring so a reviewer can spot drift at a glance.
Coverage plan
packages/proof/src/dag.test.ts— positive —createCatalogBackedModelResolvercache hit: callresolver('HIGH')twice with the same catalog; assertresolveModelSelectionFromCatalogis only invoked once (verify via a spy or by injecting a catalog that would throw on second lookup).packages/proof/src/dag.test.ts— negative —createCatalogBackedModelResolverpropagates catalog errors: pass a catalog that throws for one complexity; assert the resolver surfaces the error on first call and does not cache a bad state.packages/proof/src/dag.test.ts— edge —Cursor.models.list()transient failure: mock the SDK call to reject; assert the run exits with a readable message rather than an uncaught rejection.
Reviewer scoreboard
proof-runtime-internals: 2 findings, 2 coverage gaps, signal: HIGHtest-coverage-robustness: 0 findings, 1 coverage gap, signal: MEDcorrectness-and-contracts: 0 new findings (ModelMap shape change intentional and documented; Proof unreleased), signal: LOWSent by Cursor Automation: Flatbread PR Review
| type Complexity = 'HIGH' | 'MED' | 'LOW'; | ||
| type TaskKind = 'task' | 'pause' | 'oracle'; | ||
|
|
||
| // Keep in sync with ModelParameterValue / ModelSelection in dag.ts. |
There was a problem hiding this comment.
Canvas template types still duplicated — silent drift risk (prior finding, still open).
The ModelParameterValue and ModelSelection interfaces here live inside the embedded canvas template string, so TypeScript cannot enforce their parity with the exported types in dag.ts. The // Keep in sync comment is the only guard.
A field added to the public ModelSelection (or a param renamed) will silently be invisible in canvas rendering until someone notices at runtime.
One pragmatic option: add a prose list of the fields being mirrored to the comment so a code reviewer has a checklist:
// Keep in sync with ModelParameterValue / ModelSelection in dag.ts.
// Fields mirrored: ModelParameterValue.{id, value}; ModelSelection.{id, params?}.
// If either type gains a field, update both this template copy and the canvas render below.A stronger option (if the template compilation step is ever added) is to import and satisfies-assert the types before embedding.
Change-Id: Ia19a6f8198f5584052e4a71bca4c4503ee088c1d
There was a problem hiding this comment.
Verdict
COMMENT — Two persistent MED-severity concerns remain open across multiple prior runs (bare Cursor.models.list() and canvas template type duplication); all prior HIGH findings are now fully addressed. Test coverage for new exported functions is incomplete but not a blocker given the thoroughness of resolveModelSelectionFromCatalog tests.
Chunk-bound feedback
packages/proof/src/run_dag.ts:530-536
severity:MEDflagged-by:proof-runtime-internals, correctness-and-contracts, docs-and-positioning (3 independent runs)risk:await Cursor.models.list()is called bare — a transient network error, rate-limit, or expired API key produces an unhandled rejection that terminates the process before any task runs, with a cryptic stack trace rather than an actionable message.minimal fix:Wrap in try/catch and rethrow with a clear message:"[proof] failed to fetch model catalog — check CURSOR_API_KEY and network: <err.message>".
packages/proof/src/canvas_writer.ts:236-245
severity:LOWflagged-by:proof-runtime-internals, correctness-and-contracts (2+ runs)risk:ModelParameterValueandModelSelectionare redeclared inside the canvas template string. TypeScript cannot enforce parity withdag.ts; a new required field added toModelSelectionwill silently not appear in canvas rendering until a runtime mismatch is noticed.minimal fix:Add a snapshot/integration test that round-trips a parameterisedTaskStatethroughinitialRunState→ canvas render → check that params appear in the output string, so the drift is caught automatically.
Consensus findings
-
validateModelMapplain-string rejection — FULLY ADDRESSED. Prior runs (bc-ff4, bc-8712ac) flagged thatvalidateModelMapwould reject string model IDs. The current PR correctly callsnormalizeModelSelection(which accepts bothstring | ModelSelection) instead ofvalidateModelSelection. TestsvalidateModelMap accepts plain string model idsandvalidateModelMap accepts model selection objects with paramsconfirm correctness. Thread PRRT_kwDOGV8TsM6A87Sm resolved. -
scoreVariant/chooseMatchingVarianttie-breaking — FULLY ADDRESSED. Tests for highest-scoring variant, equal-score tie-break to catalog default, and no-match error are present indag.test.ts. -
validateModelIdnaming — FULLY ADDRESSED. Renamed tovalidateNonEmptyString, eliminating the misleading model-ID-specific name.
Disputed findings
None.
Coverage plan
packages/proof/src/dag.test.ts— positive:createCatalogBackedModelResolverreturns cloned selections and caches per complexity (call resolver twice for same complexity, verify same value but distinct object reference)packages/proof/src/dag.test.ts— negative:createCatalogBackedModelResolverpropagates catalog errors (unknown model id → thrown error reaches caller)packages/proof/src/dag.test.ts— positive:formatModelSelectionrenders model-only as bare id string and model+params as"id (k=v, k=v)"formatpackages/proof/src/dag.test.ts— edge:normalizeModelSelectionwith{ id: 'x', params: [] }is treated as a no-params selection (same as{ id: 'x' })packages/proof/src/dag.test.ts— positive:isPauseTask/isOracleTasktype guards return correct boolean for each kindpackages/proof/src/dag.test.ts— negative:createModelSelectionResolverthrows for unknown complexity string (e.g.'EXTREME')
Suggested follow-ups
Cursor.models.list()error handling (MED, carried from 3 prior runs): wrap the startup catalog fetch in a try/catch with a user-friendly error message.- Canvas template type drift: consider a build-time code-generation step or at minimum a snapshot test to catch
ModelSelectiondrift betweendag.tsand the embedded template. parseDAGstructural validation tests: duplicate task IDs, unknowndepends_onreferences, and cycle detection have no test coverage — these paths exist but are not exercised.
Reviewer scoreboard
proof-runtime-internals: partial output (runner timeout), ~6 findings surfaced, 4 coverage gaps, signal:HIGH — identified caching redundancy, legacy fallback safety, formatModelSelection persistence risktest-coverage-robustness: partial output (runner timeout), ~8 coverage gaps identified, signal:HIGH — thorough enumeration of untested exported functionscorrectness-and-contracts: partial output (runner timeout), signal:MED — confirmed normalizeModelSelection backward compat; canvas type drift flaggeddocs-and-positioning: completed, 2 LOW findings, 3 coverage gaps, signal:MED — README migration note gap identified
Sent by Cursor Automation: Flatbread PR Review
| @@ -504,9 +537,23 @@ async function main(): Promise<void> { | |||
| ), | |||
There was a problem hiding this comment.
Cursor.models.list() called with no error handling — 3rd consecutive run flagging this (still open).
A transient network error, rate-limit, or expired CURSOR_API_KEY produces an unhandled rejection here, killing the entire run before any task executes. The user sees a bare stack trace with no actionable guidance.
Suggested fix:
let catalog: Awaited<ReturnType<typeof Cursor.models.list>>;
try {
catalog = await Cursor.models.list();
} catch (err) {
throw new Error(
`[proof] failed to fetch Cursor model catalog — check CURSOR_API_KEY and network: ${
err instanceof Error ? err.message : String(err)
}`
);
}| type TaskKind = 'task' | 'pause' | 'oracle'; | ||
|
|
||
| // Keep in sync with ModelParameterValue / ModelSelection in dag.ts. | ||
| interface ModelParameterValue { |
There was a problem hiding this comment.
Canvas template types still manually duplicated — compile-time sync enforcement missing.
ModelParameterValue and ModelSelection are redeclared inside the embedded template string. TypeScript cannot enforce parity with dag.ts; a new required field (e.g. weight?: number) added to ModelSelection in dag.ts will silently not appear in the canvas render until a runtime mismatch surfaces.
A snapshot/integration test that exercises initialRunState with a parameterised task and asserts the rendered params string would catch this drift automatically.
| }; | ||
| } | ||
|
|
||
| export function createCatalogBackedModelResolver( |
There was a problem hiding this comment.
createCatalogBackedModelResolver has zero test coverage despite being new production code.
This function wraps the base resolver with caching and catalog validation. The cache correctness (returns clones, distinct object references per call) and error propagation (unknown model id bubbles to caller) are both untested.
Suggested additions to dag.test.ts:
- Positive: call resolver twice for same complexity → same value, distinct reference (clone check)
- Negative: catalog missing the model id → error reaches caller
- Edge: all three complexity levels resolved consistently when overrides mix string + object forms


Summary of changes
Adds SDK-style model selections to Proof DAG/model-file config so complexity mappings specify
{ id, params? }, validates those selections againstCursor.models.list()at run time, expands partial param selections to valid preset variants before creating Cursor SDK agents, removes the deprecatedcreateModelResolverhelper, and migrates checked-in Proof configs/examples to object-based selections.Closes #
Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
Does this introduce any non-backwards compatible changes?
{ "id": "composer-2" }Does this include any user config changes?